
import

	tango.core.Memory,
	tango.core.Array ,
	tango.core.Thread ,
	tango.core.Traits,
	
	tango.net.Uri,
	tango.math.random.Random,
	tango.net.device.Berkeley,
	tango.net.InternetAddress,
	
	tango.stdc.stringz,
	Int	= tango.text.convert.Integer ,
	
	tango.io.Stdout ,
	tango.io.device.Array ,
	tango.io.device.File ,
	
	tango.time.Clock,
	tango.time.Time,
	libeventd.Types ;
	
extern(C){
	int memset(void*, ubyte, int);
}

static char[] tozString(char[] from) {
	char[] to = new char[ from.length + 1];
	memcpy(&to[0], &from[0], from.length );
	to[$-1]	= 0 ;
	return to ;
}

T RevertByte(T)(T v) {
	static assert(  isIntegerType!(T) );
	T _v		= v;
	T*	p	=  cast(T*) &( (cast(ubyte*) &_v)[ 0 .. T.sizeof ] .reverse )[0] ;
	return	*p ;
}

static bool isNull(T)(T _dg ) {
	static assert( T.sizeof is 8 );
	if( _dg is null || _dg.ptr is null|| _dg.funcptr is null ){
		return true ;
	}
	int ptr	= cast(int) _dg.ptr  ;
	if( ptr is 0 ){
		return true ;
	}
	int fptr	= cast(int) _dg.funcptr  ;
	if( fptr is 0 ){
		return true ;
	}
	return false ;
}

void TAILQ_INSERT_TAIL(char[] field, H, E )(H head, E elm){
	mixin("elm." ~ field ~ ".tqe_next	= null ; ") ;
	mixin("elm." ~ field ~ ".tqe_prev 	= head.tqh_last ; ") ;
	*head.tqh_last	= elm ;
	mixin("head.tqh_last	= &elm." ~ field ~ ".tqe_next; ") ;
}


void main(){
	WSADATA wd = void;
	WSAStartup (0x0202, &wd);
	auto base	= event_base_new();
	evthread_use_windows_threads();
	// event_base_start_iocp(base);
	Dns.Init(base);
	Proxy_Server.Init("127.0.0.1", 1080, base);
	
	timeval tv	= {99999999,0} ;
	event_base_loopexit(base, &tv);
	event_base_dispatch(base);
}

struct Dns {
	alias Dns* 			This ;
	static event_base*	_base ;
	static evdns_base*	_dns ;
	
	static void Init(event_base* base ) {
		assert(base !is null ) ;
		_base	= base ;
		_dns		= evdns_base_new(_base , 0 );
		assert(_dns !is null ) ;
		// evdns_set_log_fn(&logfn);
		int ret	= evdns_base_config_windows_nameservers(_dns);
		assert(ret is 0) ;
	}
	
	static extern(C)	void logfn(int is_warn,  char *msg) {
		log(__LINE__, "{},{}", is_warn, fromStringz(msg) );
	}
	
	static extern(C)	void Server_CallBack(int result, char type, int count, int ttl, void *addresses, void *ctx) {
		This _self	= cast(This) ctx ;
		_self._ts	= Clock.now.unix.seconds ;
		_self._ret	= result ;
		_self._stat	= 2 ; 
		if( _self._ret is 0 ) {
			_self._ttl		= ttl ;
			_self._ip		= new ubyte[ count *  addresses.sizeof  ] ;
			memcpy(&_self._ip[0], addresses,   _self._ip.length  ) ;
		}
		assert( _self._dg !is null ) ;
		while( _self._dg.length > 0 ) {
			auto _dg	= _self._dg[0] ; 
			assert(_dg !is null  && _dg.ptr !is null && _dg.funcptr !is null);
			_dg( _self );
			_self._dg	= _self._dg[ 1 .. $];
		}
	}
	
	static This[]	list = [] ;
	
	alias void delegate(This) Dg_Type ;
	char[]	_dn	;
	Dg_Type	_dg[] ;
	int		_stat	;
	int		_ret	;
	long		_ts	;
	int		_ttl	;
	ubyte[]	_ip	;
	
	
	static void Query(char[] dn , Dg_Type dg) {
		This ptr	= null ;
		foreach( _dns; list) {
			if( dn == _dns._dn[0..$-1] ) {
				ptr	= _dns ;
				break ;
			}
		}
		ubyte[4] ipv ;
		bool	is_ip	= isIp( dn, ipv );
		if(  ptr is null ) {
			ptr		= new Dns ;
			ptr._dn	= new char[ dn.length + 1 ] ;
			memcpy(&(ptr._dn)[0], &dn[0], dn.length );
			ptr._dn[$-1]	= 0 ;
			list		~= ptr ;
			if( is_ip ) {
				ptr._ip	= ipv.dup ;
			}
		}
		long ts	=  Clock.now.unix.seconds ;
		if( is_ip || ts - ptr._ts <= ptr._ttl ) {
			dg(ptr);
		} else {
			ptr._dg	~= dg ;
			if( ptr._stat !is 1 ){
				ptr._stat	= 1 ;
				evdns_base_resolve_ipv4(_dns, ptr._dn.ptr , 0, &Server_CallBack, ptr);
			}
		}
	}
	
	static bool isIp(char[] ip, ubyte[4] ipv) {
		static bool CheckNumber(char[] ip , ref int i , ref ubyte ipv ){
			int j	= i ;
			for( ; j < ip.length ; j ++) {
				if( ip[j] < '0' || ip[j] > '9'  ) {
					break;
				}
			}
			int l	= j - i;
			if( l > 3 ){
				return false ;
			}
			int v	= 0 ;
			for ( int k = 0 ; k  < l; k++) {
				ubyte _v	= ip[ i + l - k -1 ] - '0' ;
				if( k is 0 ){
					v	+= _v ;
				}else if( k is 1 ){
					v	+= _v * 10 ;
				}else if( k is 2 ){
					v	+= _v * 100 ;
				}
			}
			if( v > 255 ){
				return false ;
			}
			ipv	= v ; 
			i	= j ;
			return  true ;
		}
		int i	=  0 ;
		if( !CheckNumber(ip, i, ipv[0] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[1] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[2] )  || i >=ip.length || ip[i++] !is '.'  ){
			return false ;
		}
		if( !CheckNumber(ip, i, ipv[3] ) ){
			return false ;
		}
		return ip.length is i  ;
	}
}




struct Proxy_Server {
	alias Proxy_Server*	This ;
	event_base*		_base ;
	evconnlistener*		_listener ;
	Address			_addr ;
	Berkeley			_bs ;
	
	static This Init(char[] ip , ushort port , event_base* base ) {
		assert(base !is null ) ;
		This		p	= new Proxy_Server ;
		p._addr		= new InternetAddress(ip, port);
		p._base		= base ;
		p._bs.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP);
		p._bs.bind( p._addr );
		p._bs.blocking(false);
		
		// LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE|LEV_OPT_CLOSE_ON_EXEC
		p._listener	= evconnlistener_new(p._base, &Accept_Cb,  p, LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE, 32 , p._bs.handle) ;
		Stdout.formatln("Socks5 Proxy Server run at {} , TCP Only .", p._addr.toString ).flush ;
		return p ;
	}
	
	static extern(C) void Accept_Cb(evconnlistener* listener, evutil_socket_t fd, sockaddr* address, int len, void* ctx){
		This _self		= cast(This) ctx ;
		Proxy_Req.Init(fd,  address , _self._base );
	}
}


struct Proxy_Req {
	alias Proxy_Req*		This ;
	event_base*		_base ;
	bufferevent*		_lbev, _rbev ;
	S5_Req			s5_req, s5_reply ;
	sockaddr			_local_addr , _remote_addr, _self_addr ;
	Berkeley			_rbs ;
	
	void delegate()		_onLocalRead, _onRemoteRead , _onLocalTimeout, _onRemoteTimeout ;
	void delegate()		_onLocalWrite, _onRemoteWrite, _onRemoteConected ;
	
	static bool readBev(bufferevent* bev, ubyte[] tmp, bool delegate(ubyte[]) dg) {
		auto be	= bufferevent_get_input(bev);
		while( true ){
			int i	= evbuffer_remove(be, tmp.ptr, tmp.length );
			if( i is 0 || i > tmp.length ){
				break;
			}
			if( !dg( tmp[0..i] ) ) {
				return false ;
			}
		}
		return true ;
	}
	
	static This Init(evutil_socket_t fd, sockaddr* _addr, event_base* base ) {
		assert(base !is null ) ;
		This		p	= new Proxy_Req ;
		p._base		= base ;
		memcpy(&p._local_addr, _addr, sockaddr.sizeof );
		p._lbev		= bufferevent_socket_new( base , fd ,  bufferevent_options.BEV_OPT_CLOSE_ON_FREE| bufferevent_options.BEV_OPT_DEFER_CALLBACKS);
		assert(p._lbev !is null);
		
		bufferevent_setcb(p._lbev, &lRead_Cb,  &lWrite_Cb, &lError_Cb, p);
		
		bufferevent_enable(p._lbev, 	EV_READ|EV_WRITE);
		
		p._onLocalRead	= &(p.onAccept);
		p._onLocalWrite	= &(p.onEmptyWrite);
		
		p._onLocalTimeout	= &(p.onLocalTimeout);
		p._onRemoteTimeout	= &(p.onRemoteTimeout);
		return p ;
	}
	
	void free() {
		log(__LINE__, "free");
		if( _lbev !is null ) {
			bufferevent_free( _lbev );
			_lbev	= null ;
		}
		if( _rbev !is null ){
			bufferevent_free( _rbev );
			_rbev	= null ;
		}
	}
	
	static extern(C) void lRead_Cb(bufferevent* bev, void* ctx){
		This _this	= cast(This) ctx ;
		if( isNull( _this._onLocalRead ) ){
			_this.free();
		}else{
			_this._onLocalRead() ;
		}
	}
	static extern(C) void lWrite_Cb(bufferevent* bev, void* ctx){
		This _this	= cast(This) ctx ;
		if( isNull( _this._onLocalWrite ) ){
			_this.free();
		}else{
			 _this._onLocalWrite() ;
		}
	}
	static extern(C) void lError_Cb(bufferevent* bev, short opt, void* ctx){
		This _this	= cast(This) ctx ;
		
		if( opt & BEV_EVENT_TIMEOUT | opt & BEV_EVENT_ERROR ) {
			_this._onLocalTimeout() ;
			return ;
		}
		
		dump_ev(opt);
		log( __LINE__, "lError_Cb unhandle event:{}", opt);
	}
	static extern(C) void rRead_Cb(bufferevent* bev, void* ctx){
		This _this	= cast(This) ctx ;
		if( isNull( _this._onRemoteRead ) ){
			_this.free();
		}else{
			 _this._onRemoteRead() ;
		}
	}
	static extern(C) void rWrite_Cb(bufferevent* bev, void* ctx){
		This _this	= cast(This) ctx ;
		if( isNull( _this._onRemoteWrite ) ){
			_this.free();
		}else{
			 _this._onRemoteWrite() ;
		}
	}
	static extern(C) void rError_Cb(bufferevent* bev, short opt, void* ctx){
		This _this	= cast(This) ctx ;
		if (opt & BEV_EVENT_ERROR) {
			_this.free;
			return ;
		}
		
		if (opt & BEV_EVENT_CONNECTED) {
			if( isNull( _this._onRemoteConected) ){
				log( __LINE__, "rError_Cb _onRemoteConected is null ");
				_this.free;
			}else{
				_this._onRemoteConected() ;
			}
			return ;
		}
		if( opt & BEV_EVENT_TIMEOUT | opt & BEV_EVENT_ERROR ) {
			_this._onRemoteTimeout() ;
			return ;
		}
		
		dump_ev(opt);
		log( __LINE__, "rError_Cb unhandle event:{}", opt);
	}
	
	void onLocalTimeout(){
		free;
	}
	
	void onRemoteTimeout(){
		free;
	}
	
	void onEmptyWrite() {
		// log( __LINE__, "onEmptyWrite");
	}
	
	void doClose() {
		log( __LINE__, "onClose");
		free() ;
	}
	
	void onAccept() {
		//log( __LINE__, "onAccept");
		ubyte[6]	tmp;
		ubyte[2]	ret ;
		ret[0]	= 0x5 ;
		ret[1]	= 0xff ;
		readBev(_lbev ,  tmp, (ubyte[] _tmp) {
			// log( __LINE__, "{}", _tmp);
			if( _tmp[0] != 0x5 ){
				return false ;
			}
			ubyte len	= _tmp[1] ;
			ubyte* ptr = &_tmp[2] ;
			ubyte* eptr = &_tmp[$] ;
			for( int i = 0 ; i < len; i++ ){
				if( ptr > eptr ) {
					return false ;
				}
				if( *ptr is 0x2 ) {
					ret[1]	= 0x2 ;
				}
				ptr++;
			}
			return true;
		});
		_onLocalRead	= &doClose ;
		if( ret[1] is 0x2 ){
			_onLocalWrite	= &onAuth ;
		}else {
			_onLocalWrite	= &doClose ;
		}
		evbuffer* ob	= bufferevent_get_output(_lbev)  ;
		evbuffer_add(ob, ret.ptr, ret.length );
	}
	
	void onAuth() {
		_onLocalRead	= &onAuthDone ;
	}
	
	void onAuthDone(){
		//log( __LINE__, "onAuthDone", __LINE__);
		ubyte[1024]	tmp;
		ubyte[2]		ret ;
		char[]		user, pw ;
		ret[0]		= 0x5 ;
		readBev(_lbev ,  tmp, (ubyte[] _tmp) {
			if( _tmp[0] !is 0x1 || _tmp.length < 6 ){
				return false ;
			}
			ret[0]		= _tmp[0];
			ubyte len	= _tmp[1] ;
			_tmp		= _tmp[2..$];
			if( len >= _tmp.length - 2 ){
				return false ;
			}
			user	= cast(char[]) _tmp[ 0 .. len ] ;
			ubyte len2	= _tmp[len] ;
			pw	= cast(char[]) _tmp[ len + 1 .. $];
			if( len2 != pw.length ){
				return false ;
			}
			return true;
		});
		
		log(__LINE__, "新连接 用户:{}, 密码:{}", user, pw);
		
		_onLocalRead	= &doClose ;
		
		if( user.length is 0 || pw.length is 0 ){
			ret[1]	= 0x5 ;
			_onLocalWrite	= &doClose ;
		}else{
			ret[1]	= 0x0 ;
			_onLocalWrite	= &onRequest ;
		}
		evbuffer* ob	= bufferevent_get_output(_lbev)  ;
		evbuffer_add(ob, ret.ptr, ret.length );
	}
	
	void onRequest(){
		_onLocalRead	= &onRequestDone ;
	}
	
	void onRequestDone(){
		//log( __LINE__, "onRequestDone");
		ubyte[1024]	tmp;
		bool			isReq ;
		readBev(_lbev ,  tmp, (ubyte[] _tmp) {
			isReq	= s5_req.loadFrom(_tmp);
			return true;
		});
		s5_reply.atyp		= s5_req.atyp ;
		if( !isReq || s5_req.ip6 !is null ) {
			s5_reply.rep	= S5_Req.REP.ProxyFailure ;
			sendReply();
			return ;
		}
		_onLocalRead	= &doClose ;
		_onLocalWrite	= &onEmptyWrite ;
		
		// log( __LINE__, "{}:  isReq={}  ip = {}:{}",  isReq, s5_req.addr[0..4],  s5_req.port) ;
		
		s5_req.copyAddr( &_remote_addr);
		if( s5_req.ip !is null ) {
			Dns.Query(s5_req.ip ,  &onDnsQuery);
		}else {
			// doConnect
			doConnect() ;
		}
	}
	
	void sendReply(){
		auto bu		= new Array(1024, 1024);
		s5_reply.copyTo(bu);
		assert(bu !is null);
		
		evbuffer* ob	= bufferevent_get_output(_lbev)  ;
		assert(ob !is null);
		
		evbuffer_add(ob, bu.slice.ptr, bu.slice.length );
		delete bu ;
	}
	
	void onDnsQuery(Dns* dns){
		if(dns._ret !is 0 ) {
			_onLocalRead	= &doClose ;
			_onLocalWrite	= &doClose ;
			s5_reply.rep	= S5_Req.REP.HostFailure ;
			sendReply();
			return ;
		}
		auto paddr	= cast(S5_Req._sockaddr*) &_remote_addr;
		memcpy( &paddr._ip[0], &dns._ip[0], 4 );
		doConnect() ;
	}
	
	void doConnect() {
		_rbs.open(AddressFamily.INET, SocketType.STREAM, ProtocolType.TCP) ;
		_rbs.blocking(false);
		
		_rbev		= bufferevent_socket_new( _base ,  _rbs.handle ,  bufferevent_options.BEV_OPT_CLOSE_ON_FREE| bufferevent_options.BEV_OPT_DEFER_CALLBACKS);
		assert(_rbev !is null);
		
		bufferevent_settimeout(_rbev, 120,  120 );
		bufferevent_setcb(_rbev, &rRead_Cb,  &rWrite_Cb, &rError_Cb, this);
		bufferevent_enable(_rbev, EV_WRITE );
		
		_onRemoteConected	= &onConnected ;
		int ret	= bufferevent_socket_connect(_rbev,  &_remote_addr , _remote_addr.sizeof );
		assert(ret is 0);
		
		// log( __LINE__, "doConnect	{}",  (cast(ubyte*) &_remote_addr)[0.._remote_addr.sizeof] );
	}
	
	void onConnected() {
		bufferevent_enable(_rbev, EV_READ );
		
		s5_reply.rep	= S5_Req.REP.OK ;
		s5_reply.atyp	= S5_Req.ATYP.IPV4 ;
		
		auto addr		= cast(IPv4Address) _rbs.localAddress ;
		s5_reply.port	= addr.port ;
		uint iaddr		= addr.addr ;
		RevertByte(iaddr);
		memcpy(&s5_reply.addr[0], &iaddr, 4);
		
		bufferevent_enable(_lbev, EV_WRITE );
		_onLocalWrite	= &afterConnected ;
		sendReply();
	}
	
	void afterConnected(){

		bufferevent_enable(_lbev, EV_READ | EV_TIMEOUT  | EV_WRITE );
		bufferevent_enable(_rbev, EV_READ  | EV_TIMEOUT | EV_WRITE );
		
		_onRemoteWrite	= &onEmptyWrite ;
		_onLocalWrite	= &onEmptyWrite ;
		
		_onLocalRead	= &localRead;
		_onRemoteRead	= &remoteRead;
	}
	
	void localRead(){
		ubyte[1024 *64 ]	tmp;
		
		evbuffer* ob	= bufferevent_get_output(_rbev)  ;
		assert(ob !is null);
		readBev(_lbev ,  tmp, (ubyte[] _tmp) {
			evbuffer_add(ob, _tmp.ptr, _tmp.length );
			return true;
		});
	}
	
	void remoteRead(){
		ubyte[1024  *64 ]	tmp;

		evbuffer* ob	= bufferevent_get_output(_lbev)  ;
		assert(ob !is null);
		readBev(_rbev ,  tmp, (ubyte[] _tmp) {
			evbuffer_add(ob, _tmp.ptr, _tmp.length );
			return true;
		});
	}
}

enum S5AuthType {
	NoAuth ,
	GSSAPI ,
	User ,
	IANA ,
	Private	= 0x80 ,
	Nonw		= 0xff ,
}

struct S5_Req {
	enum ATYP {
		IPV4		= 0x1,
		DN		= 0x3,
		IPV6		= 0x4,
	}
	enum CMD {
		CONNECT	= 0x1, 
		BIND			,
		UDP_ASSOCIATE ,
	}
	enum REP {
		OK , // 成功
		ProxyFailure , // 普通的SOCKS服务器请求失败
		ProxyDeny , //现有的规则不允许的连接
		NetFailure, // 网络不可达
		HostFailure, // 主机不可达
		ConnectDeny , // 连接被拒
		TimeOut , // 超时
		UnKnowCmd , // 不支持的命令
		UnKnowAddr ,  // 不支持的地址类型
		UnKnow	= 0xff 
	}
	ubyte	ver	= 0x5 ;
	ubyte	rep ;
	ubyte	rsv ;
	ubyte	atyp ;
	ubyte[255]	addr ; // 16, 4
	ushort	port ;
	
	alias rep	cmd ;
	
	ubyte	alen ;
	
	static struct _sockaddr {
		ushort	_family ;
		ushort	_port ;
		ubyte[4]	_ip ;
	}
	bool loadFrom(ubyte[] tmp){
		if( tmp.length < 10 ){
			return false ;
		}
		if( tmp[0] != 0x5 ){
			return false ;
		}
		cmd	= tmp[1];
		rsv	= 0 ; // 2
		atyp	= tmp[3] ;
		
		ubyte* ptr		= &tmp[4] ;
		ubyte* eptr	= &tmp[$-1] ;
		memset(&addr[0], 0, addr.length); 
		switch(atyp){
			case ATYP.IPV4: // 4 byte
				memcpy(&addr[0], ptr, 4);
				ptr	+= 4;
				alen	= 4 ;
				break;
			case ATYP.IPV6: // 16 byte
				memcpy(&addr[0], ptr, 16);
				ptr	+= 16 ;
				alen	= 16 ;
				break;
			case ATYP.DN: // x byte
				ubyte len	= *ptr ;
				ptr	= ptr+1 ;
				bool	isLen	= eptr - ptr >= len ;
			
				if( !isLen ){
					return false ;
				}
				
				alen	= len  ;
				memcpy(&addr[0], ptr, alen);
				ptr	+= len ;
				break;
			default:
				return false ;
		}
		
		if( eptr - ptr < 0 ){
			return false ;
		}
		
		ubyte* portz	= cast(ubyte*) &port;
		portz[0]		= ptr[1];
		portz[1]		= ptr[0];
		return true ;
	}
	
	char[] ip() {
		if( atyp != ATYP.DN ) {
			return null ;
		}
		return cast(char[]) addr[ 0 .. alen] ;
	}
	
	
	ubyte[] ip4(){
		if( atyp != ATYP.IPV4 ){
			return null ;
		}
		return addr[0..4] ;
	}
	
	ubyte[] ip6(){
		if( atyp != ATYP.IPV6 ){
			return null ;
		}
		return addr[0..16] ;
	}
	
	void copyTo(Array bu) {
		ubyte[16]	tmp ;
		tmp[0]	= ver ;
		tmp[1]	= cmd ;
		tmp[2]	= rsv ;
		tmp[3]	= atyp ;
		bu( tmp[0..4] );
		
		switch(atyp){
			case ATYP.IPV4: // 4 byte
				bu( addr[0..4] );
				break;
			case ATYP.IPV6: // 16 byte
				bu( addr[0..16] );
				break;
			case ATYP.DN: // x byte
				ubyte[1] len ;
				foreach( int i, c; addr){
					if( c is 0 ) {
						len[0]	= i ;
						if( len[0] < 0 ){
							len[0]	= 4 ;
						}
						bu( len );
						int	_len	= len[0]; 
						bu( addr[ 0 .. _len]  );
						break;
					}
				}
				break;
			default:
				atyp	= ATYP.IPV4 ;
				bu( addr[0..4] );
		}
		
		ubyte* portz	= cast(ubyte*) &port ;
		tmp[0]	= portz[1] ;
		tmp[1]	= portz[0] ;
		bu( tmp[0..2] );
	}
	
	void copyAddr(sockaddr* _addr){
		_sockaddr* paddr	= cast(_sockaddr*) _addr ;
		paddr._family	= AddressFamily.INET ;
		paddr._port		= RevertByte( port ) ;
		if( atyp != ATYP.IPV4 ){
			return  ;
		}
		memcpy(&(paddr._ip[0]), &addr[0], 4);
	}
}

void log(T...)(int ln, T t){
	Stdout.format("line:{}	", ln);
	Stdout.formatln(t).flush;
}

void dump_ev(int opt){
	
	if( opt & EV_TIMEOUT ) {
		Stdout.format("EV_TIMEOUT, ");
	}
	if( opt & EV_READ ) {
		Stdout.format("EV_READ, ");
	}
	if( opt & EV_WRITE ) {
		Stdout.format("EV_WRITE, ");
	}
	if( opt & EV_SIGNAL ) {
		Stdout.format("EV_SIGNAL, ");
	}
	if( opt & EV_PERSIST ) {
		Stdout.format("EV_PERSIST, ");
	}
	if( opt & EV_ET ) {
		Stdout.format("EV_ET, ");
	}
	
	Stdout.newline;
	if( opt & BEV_EVENT_READING ) {
		Stdout.format("BEV_EVENT_READING, ");
	}
	if( opt & BEV_EVENT_WRITING ) {
		Stdout.format("BEV_EVENT_WRITING, ");
	}
	if( opt & BEV_EVENT_EOF ) {
		Stdout.format("BEV_EVENT_EOF, ");
	}
	if( opt & BEV_EVENT_ERROR ) {
		Stdout.format("BEV_EVENT_ERROR, ");
	}
	if( opt & BEV_EVENT_TIMEOUT ) {
		Stdout.format("BEV_EVENT_TIMEOUT, ");
	}
	if( opt & BEV_EVENT_CONNECTED ) {
		Stdout.format("BEV_EVENT_CONNECTED, ");
	}
	Stdout.newline;
	
}